//
//  RTFParser.swift
//  XliffTranslator
//
//  Created by 杨岑 on 2018/9/28.
//  Copyright © 2018年 杨岑. All rights reserved.
//

import Cocoa

private let kAngledNil = "<nil>"

protocol RTFSection: CustomStringConvertible {
}

/// Represents an RTF group enclosed between { and }.
class RTFGroup: RTFSection {
    var sections: [RTFSection]?
    
    var description: String {
        var s = "\(type(of: self)): sections ="
        if let sections = self.sections {
            for section in sections {
                s.append("  \(section)\n")
            }
        } else { s.append(kAngledNil) }
        return s
    }
}


/// Represents an RTF control keyword.
class RTFCommand: RTFSection {
    var meta: String?
    var terminator: String?
    
    init() {}
    
    init(_ meta: String) {
        self.meta = meta
    }
    
    var description: String {
        return "\(type(of: self)): \(meta ?? kAngledNil)"
    }
}

/// A marker class for newline. Used by RTFParser
/// to represent all components of an RTF document.
class RTFNewLine: RTFSection {
    var description: String {
        return "\(type(of: self)): Marker class"
    }
}

/// Represents a source text section. The source
/// string is the original text in RTF document.
/// Use RTFParser.decode(source:) function to convert
/// it to plain readable text.
class RTFSourceText: RTFSection {
    var source: String?
    
    init(_ source: String) {
        self.source = source
    }
    
    var description: String {
        return "\(type(of: self)): source = \(source ?? kAngledNil)"
    }
}


/// This class implements very loose RTF parser.
/// It parses source RTF text into a structured
/// tree of RTFSection-based objects. The top object
/// is the enclosing \rtf1 group.
class RTFParser: NSObject {
    static let kOpeningBraceChar: Character = "{"
    static let kClosingBraceChar: Character = "}"
    static let kEscapeChar: Character = "\\"
    static let kSpaceChar: Character = " "
    static let kSemicolonChar: Character = ";"
    static let kAsteroidChar: Character = "*"
    static let kSingleQuoteChar: Character = "'"
    static let kCRChar: Character = "\r"
    static let kLFChar: Character = "\n"
    
    private var reader: StringReader!
    private(set) var errorPosition: String.Index?
    
    func parse(file filename: String) -> RTFGroup? {
        if let rtfString = try? String(contentsOfFile: filename) {
            return parse(rtfSource: rtfString)
        }
        return nil
    }
    
    func parse(rtfSource: String) -> RTFGroup? {
        reader = StringReader(rtfSource)
        var done = false
        
        // Skip whitespaces
        skipws: repeat {
            if let c = reader.readChar() {
                switch c {
                case RTFParser.kSpaceChar,
                     RTFParser.kCRChar,
                     RTFParser.kLFChar:
                    done = true
                default:
                    reader.unwind()
                    break skipws
                }
            }
        } while !done
        
        // First non-whitespace character must be the opening brace
        if let c = reader.readChar() {
            if c == RTFParser.kOpeningBraceChar {
                return parseGroup()
            }
        }
        return nil
    }
    
    private func parseGroup() -> RTFGroup? {
        let group = RTFGroup()
        var text = String()
        var done = false
        group.sections = [RTFSection]()
        
        func collectText() {
            if text.count > 0 {
                //print("\(reader.position.encodedOffset) \(text)")
                group.sections!.append(RTFSourceText(text))
                text.removeAll()
            }
        }
        
        repeat {
            if let c = reader.readChar() {
                switch c {
                case RTFParser.kOpeningBraceChar:
                    if let subgroup = parseGroup() {
                        collectText()
                        group.sections!.append(subgroup)
                    } else {
                        return nil
                    }
                case RTFParser.kClosingBraceChar:
                    collectText()
                    done = true
                case RTFParser.kEscapeChar:
                    if let cc = reader.readChar() {
                        switch cc {
                        case RTFParser.kAsteroidChar: // Special command
                            collectText()
                            group.sections!.append(RTFCommand(String(cc)))
                        case RTFParser.kOpeningBraceChar,
                             RTFParser.kClosingBraceChar,
                             RTFParser.kEscapeChar,
                             RTFParser.kCRChar,
                             RTFParser.kLFChar: // Escaped special chars
                            text.append(c)
                            text.append(cc)
                        default:
                            reader.unwind()
                            if let command = parseCommand() {
                                collectText()
                                group.sections!.append(command)
                            } else {
                                self.errorPosition = reader.position
                                return nil
                            }
                        }
                    }
                case RTFParser.kCRChar,
                     RTFParser.kLFChar: // Noise chars
                    collectText()
                    group.sections!.append(RTFNewLine())
                default:
                    text.append(c)
                }
            }
        } while !done && !reader.EOF
        return group
    }
    
    private func parseCommand() -> RTFCommand? {
        var done = false
        let command = RTFCommand()
        command.meta = String()

        if let c = reader.readChar() {
            // Handle special single quote
            if c == RTFParser.kSingleQuoteChar {
                command.meta!.append(c)
                repeat {
                    if let cc = reader.readChar() {
                        switch cc {
                        case "0"..."9",
                             "a"..."f",
                             "A"..."F",
                             ".": // Very loose parsing
                            command.meta!.append(cc)
                        case RTFParser.kSpaceChar,
                             RTFParser.kSemicolonChar: // Delimiters
                            command.terminator = String(cc)
                            done = true
                        default:
                            reader.unwind()
                            done = true
                        }
                    }
                } while !done && !reader.EOF
                return done ? command : nil
            } else { reader.unwind() }
        } else { return nil }
        
        repeat {
            if let c = reader.readChar() {
                switch c {
                case "-",
                     "0"..."9",
                     "a"..."z",
                     "A"..."Z": // Valid command chars
                    command.meta!.append(c)
                case RTFParser.kSpaceChar,
                     RTFParser.kSemicolonChar: // Delimiters
                    command.terminator = String(c)
                    done = true
                default:
                    done = true
                    reader.unwind()
                }
            }
        } while !done && !reader.EOF
        return done ? command : nil
    }
    
    static func decode(source text: String) -> String? {
        let reader = StringReader(text)
        var result = String()
        var lastChar: Character = "\0"
        
        repeat {
            if let c = reader.readChar() {
                switch c {
                case kCRChar, kLFChar:
                    if lastChar == RTFParser.kEscapeChar {
                        result.append(c)
                    } else { return nil } // Error!
                case kOpeningBraceChar,
                     kClosingBraceChar:
                    if lastChar == kEscapeChar {
                        result.append(c)
                    } else { return nil } // Error!
                case kEscapeChar:
                    if lastChar == kEscapeChar {
                        result.append(c)
                    }
                case "u":
                    if lastChar == kEscapeChar {
                        if let digits = reader.readDigits() {
                            if let cc = reader.readChar() {
                                if cc == "?" {
                                    let codepoint = Int(digits)!
                                    result.append(Character(Unicode.Scalar(codepoint)!))
                                    break
                                }
                            }
                        }
                        return nil
                    } else {
                        result.append(c)
                    }
                default:
                    result.append(c)
                }
                lastChar = c
            }
        } while !reader.EOF
        
        return result
    }
            
    static func encode(plain text: String) -> String {
        var result = String()
        for c in text {
            switch c {
            case kEscapeChar:
                result.append("\\\\")
            case kCRChar,
                 kLFChar,
                 kOpeningBraceChar,
                 kClosingBraceChar:
                result.append(kEscapeChar)
                result.append(c)
            default:
                if let value = c.unicodeScalars.first?.value {
                    if value < 128 {
                        result.append(c)
                    } else {
                        result.append("\\u\(value)?")
                    }
                }
            }
        }
        return result
    }
    
    static func compose(_ group: RTFGroup) -> String? {
        if let sections = group.sections {
            var rtfText = String()
            
            rtfText.append(RTFParser.kOpeningBraceChar)
            for section in sections {
                if let g = section as? RTFGroup {
                    if let s = RTFParser.compose(g) {
                        rtfText.append(s)
                    }
                } else if let src = section as? RTFSourceText {
                    rtfText.append(src.source!)
                } else if let cmd = section as? RTFCommand {
                    rtfText.append("\\\(cmd.meta!)")
                    if let s = cmd.terminator {
                        rtfText.append(s)
                    }
                } else if let _ = section as? RTFNewLine  {
                    rtfText.append(RTFParser.kLFChar)
                } else {
                    print("Unhandled RTF component: \(section)")
                }
            }
            rtfText.append(RTFParser.kClosingBraceChar)
            
            return rtfText
        }
        return nil
    }
}

class StringReader {
    private(set) var position: String.Index!
    private(set) var s: String;
    
    init(_ s: String) {
        self.s = s
        position = s.startIndex
    }
    
    var EOF: Bool {
        get {
            return position >= s.endIndex
        }
    }
    
    func readChar() -> Character? {
        if !EOF {
            let c = s[position]
            position = s.index(position, offsetBy: 1)
            return c
        }
        return nil
    }
    
    func peekChar() -> Character? {
        if !EOF {
            return s[position]
        }
        return nil
    }
    
    @discardableResult
    func unwind(_ by: Int = 1) -> Bool {
        if let newPosition = s.index(position, offsetBy: -by, limitedBy: s.startIndex) {
            position = newPosition
            return true
        }
        return false
    }
    
    @discardableResult
    func wind(_ by: Int = 1) -> Bool {
        if let newPosition = s.index(position, offsetBy: by, limitedBy: s.endIndex) {
            position = newPosition
            return true
        }
        return false
    }
    
    func rewind() {
        position = s.startIndex
    }
    
    @discardableResult
    func position(_ at: Int) -> Bool {
        if let newPosition = s.index(s.startIndex, offsetBy: at, limitedBy: s.endIndex) {
            position = newPosition
            return true
        }
        return false
    }
    
    func readString(_ countOfChars: Int) -> Substring? {
        if let newPosition = s.index(position, offsetBy: countOfChars, limitedBy: s.endIndex) {
            let result = s[position..<newPosition]
            position = newPosition
            return result
        }
        return nil
    }
}

extension StringReader {
    func readDigits(allowHex: Bool = false) -> String? {
        var str = String()
        
        loop: repeat {
            if let c = self.readChar() {
                switch c {
                case "0"..."9":
                    str.append(c)
                case "a"..."f",
                     "A"..."F":
                    if allowHex { str.append(c) }
                    else {
                        self.unwind()
                        break loop
                    }
                default:
                    self.unwind()
                    break loop
                }
            }
        } while !self.EOF
        return str.count > 0 ? str : nil
    }
}

extension String {
    func index(_ position: Int) -> String.Index? {
        return self.index(self.startIndex, offsetBy: position, limitedBy: self.endIndex)
    }
}
